package genetic;

import genetic.q1.AckleyPhenotype;
import genetic.q3.CliquePhenotype;
import genetic.q3.ReadFile;

import java.io.File;
import java.io.PrintWriter;
import java.util.LinkedList;
import java.util.List;

public class GeneticAlgorithm {

	List<Genotype> population;
	int populationSize;
	Phenotype prototype;
	double bestFitness = 0;
	int bestGen = 0;
	Genotype best = null;

	public GeneticAlgorithm(Phenotype prototype){
		this.population = new LinkedList<Genotype>();
		this.prototype = prototype;
	}

	public void init(int populationSize){
		this.populationSize = populationSize;
		population.clear();
		for (int i = 0; i < populationSize; i++) {
			population.add(prototype.createRandomInstance());
		}
	}

	public Genotype runAlgorithm(int numGenerations, double Pc, double Pm, boolean matingPoolRouletteWheel, String resultFile){
		PrintWriter out = openFile(resultFile);
		bestFitness = 0;
		double[] initStats = getStatistics(0);
		System.out.println("Initial population: Best - " + initStats[0] + ", Average - " + initStats[1]);
		out.println(0 + "\t" + initStats[0] + "\t" + initStats[1]);

		for (int generation=1; generation<=numGenerations; generation++){
			population = prototype.getMatingPool(population, populationSize);
			List<Genotype> newPopulation = new LinkedList<Genotype>();
			for (int i=0; i<populationSize-1; i+=2){	//breed
				Genotype p1 = population.get(i);
				Genotype p2 = population.get(i+1);
				Genotype[] offsprings;

				double rand = Math.random();
				if (rand<Pc){		//create offsprings
					offsprings = prototype.crossover(p1, p2);
				} else {			//copy parents
					offsprings = new Genotype[]{p1.clone(), p2.clone()};
				}

				newPopulation.add(prototype.mutate(offsprings[0], Pm));
				newPopulation.add(prototype.mutate(offsprings[1], Pm));
			}
			population = newPopulation;
			double[] stats = getStatistics(generation);
			System.out.println("Generation "+generation+": Best - " + stats[0] + ", Average - " + stats[1] + " , Best so far: " + best);
			out.println(generation + "\t" + stats[0] + "\t" + stats[1]);
		}

		System.out.println("Best genotype on final generation: " + best + " with fitness - " + bestFitness + ", found on generation " + bestGen);

		out.close();

		return best;
	}

	private double[] getStatistics(int gen){
		double maxFitness = 0;
		double averageFitness = 0;
		Genotype genBest = null;
		for (Genotype g: population){
			double fitness = prototype.fitness(g);
			if(maxFitness < fitness){
				maxFitness = fitness;
				genBest = g;
			}
			averageFitness = averageFitness + fitness;
		}
		averageFitness = averageFitness/(double)population.size();

		if (bestFitness < maxFitness) {
			bestFitness = maxFitness;
			best = genBest;
			bestGen = gen;
		}
		return new double[]{ maxFitness , averageFitness};
	}

	private PrintWriter openFile(String filename){
		try{
			File f = new File(filename);
			f.delete();
			f.createNewFile();
			PrintWriter out = new PrintWriter(f);
			return out;
		}catch (Exception e) {
			return null;
		}
	}

	public static void main(String[] args) {

		int run = 1;

		if (run == 3){
			ReadFile rf = new ReadFile();
			rf.openFile("c-fat500-1.txt");
			GeneticAlgorithm b = new GeneticAlgorithm(new CliquePhenotype(rf.size, rf));
			b.init(100);
			b.runAlgorithm(2000, 0.7, 0.005, false, "500q3c-fat500-1.txt");
			b.init(100);
			b.runAlgorithm(2000, 0.7, 0.005, false, "500q3c-fat500-2.txt");
			b.init(100);
			b.runAlgorithm(2000, 0.7, 0.005, false, "500q3c-fat500-3.txt");
			rf.openFile("hamming6-2.txt");
			b = new GeneticAlgorithm(new CliquePhenotype(rf.size, rf));
			b.init(100);
			b.runAlgorithm(2000, 0.7, 0.005, false, "500q3hamming6-1.txt");
			b.init(100);
			b.runAlgorithm(2000, 0.7, 0.005, false, "500q3hamming6-2.txt");
			b.init(100);
			b.runAlgorithm(2000, 0.7, 0.005, false, "500q3hamming6-3.txt");
			rf.openFile("p_hat500-1.txt");
			b = new GeneticAlgorithm(new CliquePhenotype(rf.size, rf));
			b.init(100);
			b.runAlgorithm(2000, 0.7, 0.005, false, "500q3p_hat500-3.txt");
			b.init(100);
			b.runAlgorithm(2000, 0.7, 0.005, false, "500q3p_hat500-2.txt");
			b.init(100);
			b.runAlgorithm(2000, 0.7, 0.005, false, "500q3p_hat500-3.txt");
		} else if (run == 1){
			GeneticAlgorithm b = new GeneticAlgorithm(new AckleyPhenotype());
			b.init(100);
			b.runAlgorithm(10000, 0.7, 0.001, true, "10000q1.txt");
			b.init(100);
			b.runAlgorithm(5000, 0.7, 0.001, true, "5000q1.txt");
			b.init(100);
			b.runAlgorithm(2000, 0.7, 0.001, true, "2000q1.txt");
			b.init(100);
			b.runAlgorithm(500, 0.7, 0.001, true, "500q1.txt");
			b.init(100);
			b.runAlgorithm(100, 0.7, 0.001, true, "100q1.txt");
		}
	}
}